/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    CheckOptionHandler.java
 *    Copyright (C) 1999-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core;

import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;

/**
 * Simple command line checking of classes that implement OptionHandler.
 * <p/>
 * 
 * Usage:
 * <p/>
 * <code>
 *     CheckOptionHandler -W optionHandlerClassName -- test options
 * </code>
 * <p/>
 * 
 * <!-- options-start --> Valid options are:
 * <p/>
 * 
 * <pre>
 * -D
 *  Turn on debugging output.
 * </pre>
 * 
 * <pre>
 * -S
 *  Silent mode - prints nothing to stdout.
 * </pre>
 * 
 * <pre>
 * -W
 *  Full name of the OptionHandler analysed.
 *  eg: weka.classifiers.rules.ZeroR
 *  (default weka.classifiers.rules.ZeroR)
 * </pre>
 * 
 * <pre>
 * Options specific to option handler weka.classifiers.rules.ZeroR:
 * </pre>
 * 
 * <pre>
 * -D
 *  If set, classifier is run in debug mode and
 *  may output additional info to the console
 * </pre>
 * 
 * <!-- options-end -->
 * 
 * Options after -- are used as user options in testing the OptionHandler
 * 
 * @author Len Trigg (trigg@cs.waikato.ac.nz)
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision$
 */
public class CheckOptionHandler extends Check {

    /** the optionhandler to test */
    protected OptionHandler m_OptionHandler = new weka.classifiers.rules.ZeroR();

    /** the user-supplied options */
    protected String[] m_UserOptions = new String[0];

    /** whether the tests were successful */
    protected boolean m_Success;

    /**
     * Returns an enumeration describing the available options.
     * 
     * @return an enumeration of all the available options.
     */
    @Override
    public Enumeration<Option> listOptions() {
        Vector<Option> result = new Vector<Option>();

        result.addAll(Collections.list(super.listOptions()));

        result.addElement(new Option("\tFull name of the OptionHandler analysed.\n" + "\teg: weka.classifiers.rules.ZeroR\n" + "\t(default weka.classifiers.rules.ZeroR)", "W", 1, "-W"));

        if (m_OptionHandler != null) {
            result.addElement(new Option("", "", 0, "\nOptions specific to option handler " + m_OptionHandler.getClass().getName() + ":"));

            result.addAll(Collections.list(m_OptionHandler.listOptions()));
        }

        return result.elements();
    }

    /**
     * Parses a given list of options.
     * <p/>
     * 
     * <!-- options-start --> Valid options are:
     * <p/>
     * 
     * <pre>
     * -D
     *  Turn on debugging output.
     * </pre>
     * 
     * <pre>
     * -S
     *  Silent mode - prints nothing to stdout.
     * </pre>
     * 
     * <pre>
     * -W
     *  Full name of the OptionHandler analysed.
     *  eg: weka.classifiers.rules.ZeroR
     *  (default weka.classifiers.rules.ZeroR)
     * </pre>
     * 
     * <pre>
     * Options specific to option handler weka.classifiers.rules.ZeroR:
     * </pre>
     * 
     * <pre>
     * -D
     *  If set, classifier is run in debug mode and
     *  may output additional info to the console
     * </pre>
     * 
     * <!-- options-end -->
     * 
     * @param options the list of options as an array of strings
     * @throws Exception if an option is not supported
     */
    @Override
    public void setOptions(String[] options) throws Exception {
        String tmpStr;

        super.setOptions(options);

        tmpStr = Utils.getOption('W', options);
        if (tmpStr.length() == 0) {
            tmpStr = weka.classifiers.rules.ZeroR.class.getName();
        }
        setUserOptions(Utils.partitionOptions(options));
        setOptionHandler((OptionHandler) Utils.forName(OptionHandler.class, tmpStr, null));
    }

    /**
     * Gets the current settings of the CheckClassifier.
     * 
     * @return an array of strings suitable for passing to setOptions
     */
    @Override
    public String[] getOptions() {
        Vector<String> result = new Vector<String>();

        Collections.addAll(result, super.getOptions());

        if (getOptionHandler() != null) {
            result.add("-W");
            result.add(getOptionHandler().getClass().getName());
        }

        if (m_OptionHandler != null) {
            String[] options = m_OptionHandler.getOptions();
            result.add("--");
            Collections.addAll(result, options);
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Set the OptionHandler to work on..
     * 
     * @param value the OptionHandler to use.
     */
    public void setOptionHandler(OptionHandler value) {
        m_OptionHandler = value;
    }

    /**
     * Get the OptionHandler used in the tests.
     * 
     * @return the OptionHandler used in the tests.
     */
    public OptionHandler getOptionHandler() {
        return m_OptionHandler;
    }

    /**
     * Sets the user-supplied options (creates a copy)
     * 
     * @param value the user-supplied options to use
     */
    public void setUserOptions(String[] value) {
        m_UserOptions = getCopy(value);
    }

    /**
     * Gets the current user-supplied options (creates a copy)
     * 
     * @return the user-supplied options
     */
    public String[] getUserOptions() {
        return getCopy(m_UserOptions);
    }

    /**
     * returns the success of the tests
     * 
     * @return true if the tests were successful
     */
    public boolean getSuccess() {
        return m_Success;
    }

    /**
     * Prints the given options to a string.
     * 
     * @param options the options to be joined
     * @return the options as one long string
     */
    protected String printOptions(String[] options) {
        if (options == null) {
            return ("<null>");
        } else {
            return Utils.joinOptions(options);
        }
    }

    /**
     * Compares the two given sets of options.
     * 
     * @param options1 the first set of options
     * @param options2 the second set of options
     * @throws Exception if the two sets of options differ
     */
    protected void compareOptions(String[] options1, String[] options2) throws Exception {

        if (options1 == null) {
            throw new Exception("first set of options is null!");
        }
        if (options2 == null) {
            throw new Exception("second set of options is null!");
        }
        if (options1.length != options2.length) {
            throw new Exception("problem found!\n" + "First set: " + printOptions(options1) + '\n' + "Second set: " + printOptions(options2) + '\n' + "options differ in length");
        }
        for (int i = 0; i < options1.length; i++) {
            if (!options1[i].equals(options2[i])) {

                throw new Exception("problem found!\n" + "\tFirst set: " + printOptions(options1) + '\n' + "\tSecond set: " + printOptions(options2) + '\n' + '\t' + options1[i] + " != " + options2[i]);
            }
        }
    }

    /**
     * creates a copy of the given options
     * 
     * @param options the options to copy
     * @return the copy
     */
    protected String[] getCopy(String[] options) {
        String[] result;

        result = new String[options.length];
        System.arraycopy(options, 0, result, 0, options.length);

        return result;
    }

    /**
     * returns a new instance of the OptionHandler's class
     * 
     * @return a new instance
     */
    protected OptionHandler getDefaultHandler() {
        OptionHandler result;

        try {
            result = m_OptionHandler.getClass().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            result = null;
        }

        return result;
    }

    /**
     * returns the default options the default OptionHandler will return
     * 
     * @return the default options
     */
    protected String[] getDefaultOptions() {
        String[] result;
        OptionHandler o;

        o = getDefaultHandler();
        if (o == null) {
            println("WARNING: couldn't create default handler, cannot use default options!");
            result = new String[0];
        } else {
            result = o.getOptions();
        }

        return result;
    }

    /**
     * checks whether the listOptions method works
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkListOptions() {
        boolean result;

        print("ListOptions...");

        try {
            Enumeration<Option> enu = getOptionHandler().listOptions();
            if (getDebug() && enu.hasMoreElements()) {
                println("");
            }
            while (enu.hasMoreElements()) {
                Option option = enu.nextElement();
                if (getDebug()) {
                    println(option.synopsis());
                    println(option.description());
                }
            }

            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * checks whether the user-supplied options can be processed at all
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkSetOptions() {
        boolean result;

        print("SetOptions...");

        try {
            getDefaultHandler().setOptions(getUserOptions());
            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * checks whether the default options can be processed completely or some
     * invalid options are returned by the getOptions() method.
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkDefaultOptions() {
        boolean result;
        String[] options;

        print("Default options...");

        options = getDefaultOptions();

        try {
            getDefaultHandler().setOptions(options);
            Utils.checkForRemainingOptions(options);
            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * checks whether the user-supplied options can be processed completely or some
     * "left-over" options remain
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkRemainingOptions() {
        boolean result;
        String[] options;

        print("Remaining options...");

        options = getUserOptions();

        try {
            getDefaultHandler().setOptions(options);
            if (getDebug()) {
                println("\n  remaining: " + printOptions(options));
            }
            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * checks whether the user-supplied options stay the same after settting,
     * getting and re-setting again
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkCanonicalUserOptions() {
        boolean result;
        OptionHandler handler;
        String[] userOptions;
        String[] userOptionsCheck;

        print("Canonical user options...");

        try {
            handler = getDefaultHandler();
            handler.setOptions(getUserOptions());
            if (getDebug()) {
                print("\n  Getting canonical user options: ");
            }
            userOptions = handler.getOptions();
            if (getDebug()) {
                println(printOptions(userOptions));
            }
            if (getDebug()) {
                println("  Setting canonical user options");
            }
            handler.setOptions(userOptions.clone());
            if (getDebug()) {
                println("  Checking canonical user options");
            }
            userOptionsCheck = handler.getOptions();
            compareOptions(userOptions, userOptionsCheck);

            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * checks whether the optionhandler can be re-setted again to default options
     * after the user-supplied options have been set.
     * 
     * @return index 0 is true if the test was passed, index 1 is always false
     */
    public boolean checkResettingOptions() {
        boolean result;
        String[] defaultOptions;
        String[] defaultOptionsCheck;
        OptionHandler handler;

        print("Resetting options...");

        try {
            if (getDebug()) {
                println("\n  Setting user options");
            }
            handler = getDefaultHandler();
            handler.setOptions(getUserOptions());
            defaultOptions = getDefaultOptions();
            if (getDebug()) {
                println("  Resetting to default options");
            }
            handler.setOptions(getCopy(defaultOptions));
            if (getDebug()) {
                println("  Checking default options match previous default");
            }
            defaultOptionsCheck = handler.getOptions();
            compareOptions(defaultOptions, defaultOptionsCheck);

            println("yes");
            result = true;
        } catch (Exception e) {
            println("no");
            result = false;

            if (getDebug()) {
                println(e);
            }
        }

        return result;
    }

    /**
     * Runs some diagnostic tests on an optionhandler object. Output is printed to
     * System.out (if not silent).
     */
    @Override
    public void doTests() {
        println("OptionHandler: " + m_OptionHandler.getClass().getName() + "\n");

        if (getDebug()) {
            println("--> Info");
            print("Default options: ");
            println(printOptions(getDefaultOptions()));
            print("User options: ");
            println(printOptions(getUserOptions()));
        }

        println("--> Tests");
        m_Success = checkListOptions();

        if (m_Success) {
            m_Success = checkSetOptions();
        }

        if (m_Success) {
            m_Success = checkDefaultOptions();
        }

        if (m_Success) {
            m_Success = checkRemainingOptions();
        }

        if (m_Success) {
            m_Success = checkCanonicalUserOptions();
        }

        if (m_Success) {
            m_Success = checkResettingOptions();
        }
    }

    /**
     * Main method for using the CheckOptionHandler.
     * 
     * @param args the options to the CheckOptionHandler
     */
    public static void main(String[] args) {
        CheckOptionHandler check = new CheckOptionHandler();
        runCheck(check, args);
        if (check.getSuccess()) {
            System.exit(0);
        } else {
            System.exit(1);
        }
    }
}
